#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/ide.h>
#include <asm/mach/map.h>
#include <linux/timer.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/platform_device.h>
#include <linux/atomic.h>
#include <linux/irq.h>
#include <linux/delay.h>
#include <linux/poll.h>


#define IMX6UIRQ_CNT         1
#define IMX6UIRQ_NAME        "noblockio"

#define KEY_NUM            1
#define KEY0VALUE          0X01
#define INVAKEY            0XFF

struct irq_keydesc{
    int gpio;
    int irqnum;
    unsigned char value;
    char name[10];
    irqreturn_t (*handler)(int, void *);
};

struct key_dev{
    dev_t devid;
    struct cdev cdev;
    struct class *class;
    struct device *device;
    int major;
    int minor;
    struct device_node  *nd;
    struct irq_keydesc irqkeydesc;
    struct timer_list timer;
    atomic_t keyvalue;
    atomic_t releasekey;
    unsigned char curkeynum;
    wait_queue_head_t r_wait;
};

struct key_dev keydev;

static irqreturn_t key0_handle(int irq,void *dev_id)
{
    struct key_dev *dev = (struct key_dev *)dev_id;

    dev->curkeynum = 0;
    dev->timer.data = (volatile long)dev_id;
    mod_timer(&dev->timer,jiffies+msecs_to_jiffies(5));
    return IRQ_RETVAL(IRQ_HANDLED);
}

void timer_function(unsigned long arg)
{
    unsigned char value;
    unsigned char num;
    struct irq_keydesc *keydesc;
    struct key_dev *dev = (struct key_dev*)arg;


    num = dev->curkeynum;
    keydesc = &dev->irqkeydesc;
    
    value = gpio_get_value(keydesc->gpio);
	if(value == 0){ 						/* 按下按键 */
		atomic_set(&dev->keyvalue, keydesc->value);
	}
	else{ 									/* 按键松开 */
		atomic_set(&dev->keyvalue, 0x80 | keydesc->value);
		atomic_set(&dev->releasekey, 1);	/* 标记松开按键，即完成一次完整的按键过程 */
	}  
    /* 唤醒进程 */
	if(atomic_read(&dev->releasekey)) {	/* 完成一次按键过程 */
		/* wake_up(&dev->r_wait); */
		wake_up_interruptible(&dev->r_wait);
	}
}


static int key_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &keydev;

    return 0;
}

static ssize_t key_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    int ret = 0;
    unsigned char keyvalue = 0;
    unsigned char releasekey = 0;
   // unsigned long ms = 10;
    struct key_dev *dev = filp->private_data;

    if(filp->f_flags & O_NONBLOCK){
        if(atomic_read(&dev->releasekey) == 0){
            return -EAGAIN;
        }else{
            ret = wait_event_interruptible(dev->r_wait,atomic_read(&dev->releasekey));
            if(ret){
                goto wait_error;
            }
        }
    }


    keyvalue = atomic_read(&dev->keyvalue);
    releasekey = atomic_read(&dev->releasekey);
    
    if(releasekey){
    //    printk("keyvalue = %d\r\n",keyvalue);
        if(keyvalue & 0x80){
            atomic_set(&dev->releasekey,0);
            keyvalue &= ~0x80;
            ret = copy_to_user(buf,&keyvalue,sizeof(keyvalue));
        }else{
            atomic_set(&dev->releasekey,0);
            ret = copy_to_user(buf,&keyvalue,sizeof(keyvalue));
        }
    }
    else{
        goto data_error;
    }
     return 0;

wait_error:
	return ret;
data_error:
    return -EINVAL;
}

static ssize_t key_release(struct inode *inode, struct file *filp)
{
    //struct key_dev *dev = filp->private_data;
        
    return 0;
}

unsigned int  key_poll(struct file *filp, struct poll_table_struct *wait)
{
    unsigned int mask = 0;
    struct key_dev *dev = (struct key_dev *)filp->private_data;

    poll_wait(filp,&dev->r_wait,wait);
    
    if(atomic_read(&dev->releasekey)){
        mask = POLLIN | POLLRDNORM;
    }
    return mask;
}

static struct file_operations key_fops = {
    .owner = THIS_MODULE,
    .open  = key_open,
    .read = key_read,
    .release = key_release,
    .poll = key_poll,
};

static int key_probe(struct platform_device *dev)
{
    int ret;

    atomic_set(&keydev.keyvalue,INVAKEY);
    atomic_set(&keydev.releasekey,1);

    printk("key driver and device was matched!\r\n");

    if(keydev.major){
        keydev.devid = MKDEV(keydev.major,0);
        register_chrdev_region(keydev.devid,IMX6UIRQ_CNT,IMX6UIRQ_NAME);
    }
    else
    {
        alloc_chrdev_region(&keydev.devid,0,IMX6UIRQ_CNT,IMX6UIRQ_NAME);
        keydev.major = MAJOR(keydev.devid);
        keydev.minor = MINOR(keydev.devid);
    }

    keydev.cdev.owner = THIS_MODULE;
    cdev_init(&keydev.cdev,&key_fops);
    cdev_add(&keydev.cdev,keydev.devid,IMX6UIRQ_CNT);

    keydev.class = class_create(THIS_MODULE,IMX6UIRQ_NAME);
    if(IS_ERR(keydev.class)){
        return -PTR_ERR(keydev.class);
    }

    keydev.device = device_create(keydev.class,NULL,keydev.devid,NULL,IMX6UIRQ_NAME);
    if(IS_ERR(keydev.device)){
        return PTR_ERR(keydev.device);
    }

    keydev.nd = of_find_node_by_path("/key");
    if(keydev.nd == NULL){
        printk("gpio node nost find!\r\n");
        return -EINVAL;
    }
    keydev.irqkeydesc.gpio = of_get_named_gpio(keydev.nd,"key-gpio",0);
    if(keydev.irqkeydesc.gpio < 0){
        printk("can't get key-gpio\r\n");
        return -EINVAL;
    }

    gpio_request(keydev.irqkeydesc.gpio,".irqkeydesc.gpio");
    gpio_direction_input(keydev.irqkeydesc.gpio);

    /*申请中断*/
    memset(keydev.irqkeydesc.name,0,sizeof(keydev.irqkeydesc.name));
    sprintf(keydev.irqkeydesc.name,".irqkeydesc.gpio");
    keydev.irqkeydesc.irqnum = irq_of_parse_and_map(keydev.nd,0);
    keydev.irqkeydesc.handler = key0_handle;
    keydev.irqkeydesc.value =  KEY0VALUE;
    ret = request_irq(keydev.irqkeydesc.irqnum,keydev.irqkeydesc.handler,IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,keydev.irqkeydesc.name,&keydev);
    if(ret < 0){
		printk("irq %d request failed!\r\n", keydev.irqkeydesc.irqnum);
		return -EFAULT;
	}
    init_timer(&keydev.timer);
    keydev.timer.function = timer_function;

    init_waitqueue_head(&keydev.r_wait);
    return 0;
}

static int key_remove(struct platform_device *dev)
{   
    atomic_set(&keydev.keyvalue,0);
    gpio_free(keydev.irqkeydesc.gpio);
    cdev_del(&keydev.cdev);
    del_timer_sync(&keydev.timer);	/* 删除定时器 */
    free_irq(keydev.irqkeydesc.irqnum, &keydev);
    unregister_chrdev_region(keydev.devid,IMX6UIRQ_CNT);
    device_destroy(keydev.class,keydev.devid);
    class_destroy(keydev.class);
    return 0;
}

static const struct of_device_id keys_of_match[] = {
    {.compatible = "atkalpha-keygpio"},
    {}
};

MODULE_DEVICE_TABLE(of,keys_of_match);

static struct platform_driver key_platfrom_driver = {
    .driver = {
        .name = "imx6ul-key",
        .of_match_table = keys_of_match,
    },
    .probe = key_probe,
    .remove = key_remove,
};

static int __init keydriver_init(void)
{
    return platform_driver_register(&key_platfrom_driver);
}

static void __exit keydriver_exit(void)
{
    platform_driver_unregister(&key_platfrom_driver);
}


module_init(keydriver_init);
module_exit(keydriver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("GARY");